﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;

namespace SimpleORM
{
    public class DbCReader : DReader
    {
        #region Methods

        #region Methods - Get Value

        public override object GetValue(object data, object columnParameter, object columnType)
        {
            if (columnType == null)
                columnType = DbType.Object;

            object obj = base.GetValue(data, columnParameter, columnType);
            if (obj == DBNull.Value)
                obj = null;

            return obj;
        }

        public override DataTable GetTable(object data)
        {
            DataTable dt = new DataTable();

            using (IDataReader dr = data as IDataReader)
                dt.Load(dr);

            return dt;
        }

        //public override List<Dictionary<string, object>> GetDictionary(object data)
        //{
        //    List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();

        //    using (IDataReader dr = data as IDataReader)
        //    {
        //        while (dr.Read())
        //            list.Add(GetDictionary(dr));
        //    }

        //    return list;
        //}

        //private Dictionary<string, object> GetDictionary(IDataReader dr)
        //{
        //    Dictionary<string, object> values = new Dictionary<string, object>();

        //    for (int i = 0; i < dr.FieldCount; i++)
        //    {
        //        string columnName = string.Empty;

        //        try
        //        {
        //            columnName = dr.GetName(i);

        //            values[columnName] = GetValue(dr, columnName, null);
        //        }
        //        catch (Exception ex)
        //        {
        //            string msg = string.Format(new EdProvider().
        //                GetName(EdProvider.langReadColumnValueFail), columnName, ex);

        //            Tracker.WriteLine(new TrackerObject()
        //            {
        //                Description = msg,
        //                EventID = EdProvider.langReadColumnValueFail,
        //                Grade = eMessageGrade.Exception,
        //                Source = this.GetType().FullName,
        //            });
        //        }
        //    }

        //    return values;
        //}

        #endregion

        #region Methods - Get Objects

        public override IList GetObjects(object data, ObjectContext oc)
        {
            Type listType = typeof(CDObjectList<>).MakeGenericType(oc.ElementType);
            IList list = (IList)Activator.CreateInstance(listType);
            MethodInfo addMethod = listType.GetMethod("Add");

            using (IDataReader dr = data as IDataReader)
            {
                while (dr.Read())
                    addMethod.Invoke(list, new object[] { GetObject(dr, oc) });
            }

            return list;
        }

        private object GetObject(IDataReader dr, ObjectContext oc)
        {
            if (!oc.IsClass)
                return GetFirstValue(dr, oc);

            if (oc.IsAnonymousClass)
                return GetAnonymousValue(dr, oc);

            return GetClassValue(dr, oc);
        }

        private object GetClassValue(IDataReader dr, ObjectContext oc)
        {
            object rntObj = Activator.CreateInstance(oc.ElementType);

            for (int i = 0; i < dr.FieldCount; i++)
            {
                string colName = dr.GetName(i);

                PropertyContext pc = oc.Columns[colName];

                object value = TypeConverter.To(
                    GetValueByIndex(dr, i), pc.PropertyInfo.PropertyType);

                pc.PropertyInfo.SetValue(rntObj, value, null);
            }

            return rntObj;
        }

        private object GetAnonymousValue(IDataReader dr, ObjectContext oc)
        {
            ParameterInfo[] pis = oc.ElementType.GetConstructors()[0].GetParameters();

            List<object> values = new List<object>();

            for (int i = 0; i < dr.FieldCount; i++)
                values.Add(TypeConverter.To(
                    GetValueByIndex(dr, i), pis[i].ParameterType));

            return Activator.CreateInstance(oc.ElementType, values.ToArray());
        }

        private object GetFirstValue(IDataReader dr, ObjectContext oc)
        {
            return TypeConverter.To(GetValueByIndex(dr, 0), oc.ElementType);
        }

        private object GetValueByIndex(IDataReader dr, int index)
        {
            if (dr.IsDBNull(index))
                return null;

            return dr[index];
        }


        #endregion

        #region Methods - Override

        protected override TypeConverter OnCreateTypeConverter()
        {
            return new DbCTypeConverter();
        }

        #endregion

        #region Methods - Reader

        [DReaderAttribute(DbType.Object)]
        public static object GetObject(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return dr[columnName];
        }

        [DReaderAttribute(DbType.AnsiString)]
        [DReaderAttribute(DbType.AnsiStringFixedLength)]
        [DReaderAttribute(DbType.String)]
        [DReaderAttribute(DbType.StringFixedLength)]
        [DReaderAttribute(DbType.Xml)]
        public static string GetString(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (string)dr[columnName];
        }

        [DReaderAttribute(DbType.Byte)]
        public static byte? GetByte(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (byte)dr[columnName];
        }

        [DReaderAttribute(DbType.SByte)]
        public static sbyte? GetSByte(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (sbyte)dr[columnName];
        }

        [DReaderAttribute(DbType.Int16)]
        public static short? GetInt16(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (short)dr[columnName];
        }

        [DReaderAttribute(DbType.UInt16)]
        public static ushort? GetUInt16(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (ushort)dr[columnName];
        }

        [DReaderAttribute(DbType.Int32)]
        public static int? GetInt32(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (int)dr[columnName];
        }

        [DReaderAttribute(DbType.UInt32)]
        public static uint? GetUInt32(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (uint)dr[columnName];
        }

        [DReaderAttribute(DbType.Int64)]
        public static long? GetInt64(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (long)dr[columnName];
        }

        [DReaderAttribute(DbType.UInt64)]
        public static ulong? GetUInt64(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (ulong)dr[columnName];
        }

        [DReaderAttribute(DbType.Single)]
        public static float? GetSingle(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (float)dr[columnName];
        }

        [DReaderAttribute(DbType.Decimal)]
        public static double? GetDouble(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return Convert.ToDouble(dr[columnName]);
        }

        [DReaderAttribute(DbType.Double)]
        [DReaderAttribute(DbType.Currency)]
        [DReaderAttribute(DbType.VarNumeric)]
        public static decimal? GetDecimal(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (decimal)dr[columnName];
        }

        [DReaderAttribute(DbType.Guid)]
        public static Guid? GetGuid(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (Guid)dr[columnName];
        }

        [DReaderAttribute(DbType.Boolean)]
        public static bool? GetBoolean(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (bool)dr[columnName];
        }

        [DReaderAttribute(DbType.Binary)]
        public static byte[] GetBinary(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (byte[])dr[columnName];
        }

        [DReaderAttribute(DbType.Time)]
        [DReaderAttribute(DbType.Date)]
        [DReaderAttribute(DbType.DateTime)]
        [DReaderAttribute(DbType.DateTime2)]
        [DReaderAttribute(DbType.DateTimeOffset)]
        public static DateTime? GetDateTime(IDataReader dr, string columnName)
        {
            if (dr.IsDBNull(columnName))
                return null;

            return (DateTime)dr[columnName];
        }

        #endregion

        #endregion
    }
}